﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using Microsoft.DirectX.DirectSound;
using Microsoft.DirectX;

namespace SpeechToText.STT
{
    /// <summary>
    /// Доступ к микрофону. запись звука.
    /// </summary>
    public class SoundCapture : IDisposable
    {
        #region Private Fields

        private Capture capture;
        private CaptureBuffer captureBuffer;
        private CaptureBufferDescription captureBufferDesc;
        private BufferPositionNotify[] notifyPositions;
        private Notify notify;
        private AutoResetEvent notifyEvent;
        private Thread notifyThread;
        private int readPosition = 0;
        private bool isRecording = false;
        private List<byte[]> rawSoundData;

        #endregion 
        
        #region Public Properties 

        /// <summary>Параметры записи</summary>
        public WaveFormat WavFormat { get; private set; }

        #endregion
 



        #region Constructors
        /// <summary></summary>
        /// <param name="samplingRate">Частота звука. Число в Герцах</param>
        /// <param name="bitsPerSample">Количество бит. Обично 8 или 16</param>
        /// <param name="channels">Количество каналов записи. Моно (1) или Стерео (2).</param>
        public SoundCapture(int samplingRate, short bitsPerSample, short channels)
        {
            WavFormat = WavHeader.CreateWaveFormat(samplingRate, bitsPerSample, channels);
            PeaceOfConstructor();
        }

        public SoundCapture(WaveFormat _WavFormat)
        {
            WavFormat = _WavFormat;
            PeaceOfConstructor();
        }

        #endregion



 
        #region Private Methods

        private void PeaceOfConstructor()
        {
            captureBufferDesc = new CaptureBufferDescription();
            captureBufferDesc.BufferBytes = WavFormat.AverageBytesPerSecond;
            captureBufferDesc.ControlEffects = false;
            captureBufferDesc.Format = WavFormat;
            captureBufferDesc.WaveMapped = true;

            capture = new Capture();

            captureBuffer = new CaptureBuffer(captureBufferDesc, capture);

            rawSoundData = new List<byte[]>();

            SetCaptureNotifications(); 
        }

        private void NotifyThreadWait()
        {
            do
            {
                notifyEvent.WaitOne(Timeout.Infinite, false);
 
                RecordSoundData();
            } while (isRecording);
        } 
  
        private void RecordSoundData()
        {
            try
            {
                int size = WavFormat.AverageBytesPerSecond / WavFormat.BitsPerSample;

                byte[] soundData = new byte[size];

                captureBuffer.Read(readPosition, new MemoryStream(soundData), size, LockFlag.None);

                readPosition += size;

                if (readPosition >= WavFormat.AverageBytesPerSecond - 1) readPosition = 0;

                rawSoundData.Add(soundData);

                soundData = null;
            }
            catch
            { } //!!!!!!!!!!!!!!!!!!!!!
        } 
 
        private void SetCaptureNotifications()
        {
            int blockSize = WavFormat.AverageBytesPerSecond / WavFormat.BitsPerSample;
 
            notifyPositions = new BufferPositionNotify[WavFormat.BitsPerSample];
 
            notifyEvent = new AutoResetEvent(false);
 
            IntPtr notifyHandle = notifyEvent.SafeWaitHandle.DangerousGetHandle();
 
            for (int a = 0; a < WavFormat.BitsPerSample; a++)
            {
                notifyPositions[a] = new BufferPositionNotify();
                notifyPositions[a].Offset = (blockSize * (a + 1)) - 1;
                notifyPositions[a].EventNotifyHandle = notifyHandle;
            } 
 
            notifyThread = new Thread(new ThreadStart(NotifyThreadWait));
 
            notify = new Notify(captureBuffer);
            notify.SetNotificationPositions(notifyPositions, WavFormat.BitsPerSample);
 
            notifyThread.Start();
        } 

        #endregion
 



        #region Public Methods

        public void Dispose()
        {
            if (isRecording) Stop();

            if (capture != null)
            {
                capture.Dispose();
                capture = null;
            } 
 
            if (captureBuffer != null)
            {
                captureBuffer.Dispose();
                captureBuffer = null;
            } 
 
            if (notify != null)
            {
                notify.Dispose();
                notify = null;
            } 
 
            if (notifyEvent != null)
            {
                notifyEvent.Close();
                notifyEvent = null;
            } 
 
            notifyPositions = null;
 
            if (notifyThread.ThreadState != ThreadState.Aborted)
            {
                notifyThread.Abort();
            } 

            if (rawSoundData != null) rawSoundData.Clear();
            rawSoundData = null;
        }
 
        /// <summary>"Чистые" данные полученные с микрофона</summary>
        public byte[] GetSoundDataArray()
        {
            List<byte> data = new List<byte>();
            
            for (int a = 0; a < rawSoundData.Count; a++)
            {
                for (int b = 0; b < rawSoundData[a].Length; b++)
                {
                    data.Add(rawSoundData[a].ElementAt<byte>(b));
                }
            }

            return data.ToArray();
  
        }

        public List<byte[]> GetSoundDataList()
        {
            return rawSoundData;
        }  
 
        public void Start()
        {

            if (captureBuffer != null)
            {
                captureBuffer.Start(true);
                isRecording = true;
            }
        } 
 
        public void Stop()
        {
            
            if (captureBuffer != null)
            {
                isRecording = false;
                captureBuffer.Stop();
                RecordSoundData();
            }
        } 

        public void ClearRecord()
        {
            rawSoundData.Clear();
        }

        /// <summary>Сохранить как wav файл</summary>
        /// <param name="fileName"> Путь сохранения </param>
        public void SaveToFile(string FilePath)
        {
            CreateWavResource().SaveToFile(FilePath);
        }
        
        /// <summary>
        /// Из записанных данных составит объект WavResource
        /// </summary>
        /// <returns></returns>
        public WavResource CreateWavResource()
        {
            return new WavResource(WavFormat, GetSoundDataArray());
        } 

        #endregion
    } 
}
